package com.catal.framework.sequence;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.dao.DataAccessException;

/**
 * 默认序列号服务接口实现
 * 
 */
public class DefaultSequence implements Sequence, InitializingBean,
		ApplicationListener {

	private static final Logger log = Logger.getLogger(DefaultSequence.class);

	private static final int DEFAULT_LENGTH = 10;

	private boolean autoCreate;

	/**
	 * 存放序列号定义
	 */
	private Map noMap = Collections.synchronizedMap(new HashMap(100));

	/**
	 * 存放序列号生成的实例
	 */
	private Map instanceMap = Collections.synchronizedMap(new HashMap(100));

	/**
	 * 每次从数据库获取的序列号实例数
	 */
	private int defaultBufferSize = 1;

	private SequenceDao sequenceDao;

	/**
	 * 初始化缓存
	 * 
	 */
	public void afterPropertiesSet() throws Exception {
		/* 读取所有的 序列号定义 到CACHE中 */
		Map allNo = sequenceDao.getAllNo();
		noMap.putAll(allNo);

		/* 读取所有的 序列号生成的实例 到CACHE中 */
		Map allInstance = sequenceDao.getAllInstance(defaultBufferSize);
		instanceMap.putAll(allInstance);

	}

	public void create(No no) {
		String seqDefid = no.getNoid();
		synchronized (seqDefid.intern()) {
			sequenceDao.insertNo(no);
			noMap.put(seqDefid, no);
		}
	}

	public boolean isExist(String seqDefid) {
		No no = sequenceDao.getNo(seqDefid);
		if(no != null){
			return true;
		}
		return false;
	}

	public String generate(String seqDefid) {
		// 产生的序列号
		long id;
		No no = null;
		Calendar cale = Calendar.getInstance();
		synchronized (seqDefid.intern()) {
			no = loadNO(seqDefid);

			// synchronized (seqDefid.intern()) {
//			DefaultSequence currentProxy = (DefaultSequence) AopContext
//					.currentProxy();
//			// 生成序列号
//			id = currentProxy.getNextSequence(no, cale);
			id = this.getNextSequence(no, cale);
			// }
		}
		// 根据定义生成序列号字符串
		String value = buildSequenceStr(id, no.getNolength(), no.getPrefix(),
				no.getPostfix(), cale);
		return value;
	}

	/**
	 * 生成序列号。事务应该REQUIRE_NEW。
	 * 
	 */
	public long getNextSequence(No no, Calendar cale) {
		int year = cale.get(Calendar.YEAR);
		int month = cale.get(Calendar.MONTH) + 1;
		int day = cale.get(Calendar.DAY_OF_MONTH);

		Instance ins = (Instance) instanceMap.get(no.getNoid());

		if (ins == null || needReset(no, ins, cale)) {// 需要创建新的实例
			sequenceDao.lockNo(no.getNoid());

			/* 下两行代码的意思是根据序列号的复位类型，得到序列号实例中的“年、月、日”值 */
			Instance t_ins = new Instance();
			reset(no.getNotype(), year, month, day, t_ins);
			// 根据以上获得的参数，从数据库中获取对应的实例
			ins = sequenceDao.getInstance(no.getNoid(), t_ins.getYear(), t_ins
					.getMonth(), t_ins.getDay());

			if (ins == null) {
				// 创建一个默认的序列号实例,并保存到数据库中
				ins = buildDefaultInstance(no, cale);
				int bufferSize = getBufferSize(no);
				sequenceDao.insertInstance(ins);

				// 让nextId的值为当前线程的第一个序列号值。（ins.getNextId() +
				// bufferSize）的值为当前数据库中已被获取的序列号数
				ins.setNextId(ins.getNextId() - bufferSize);
			} else {
				int bufferSize = getBufferSize(no);
				ins.setLeftNum(bufferSize);
				ins.setNextId(ins.getNextId() + bufferSize);// 在数据库中获取bufferSize个序列号。
				sequenceDao.updateInstance(ins);
				ins.setNextId(ins.getNextId() - bufferSize);// 让nextId的值为当前线程的第一个序列号值。
			}

			instanceMap.put(no.getNoid(), ins);
		} else if (ins.getLeftNum() == 0) {// 序列号已经取完了，再从数据库中取序列号。
			sequenceDao.lockNo(no.getNoid());

			/* 下两行代码的意思是根据序列号的复位类型，得到序列号实例中的“年、月、日”值 */
			Instance t_ins = new Instance();
			reset(no.getNotype(), year, month, day, t_ins);
			// 根据以上获得的参数，从数据库中获取对应的实例
			ins = sequenceDao.getInstance(no.getNoid(), t_ins.getYear(), t_ins
					.getMonth(), t_ins.getDay());
			int bufferSize = getBufferSize(no);
			ins.setLeftNum(bufferSize);
			ins.setNextId(ins.getNextId() + bufferSize);// 在数据库中获取bufferSize个序列号。
			sequenceDao.updateInstance(ins);

			ins.setNextId(ins.getNextId() - bufferSize);// 设置下一个序列号，让nextId的值为当前线程的第一个序列号值。

			instanceMap.put(no.getNoid(), ins);
		}

		int bufferSize = getBufferSize(no);

		int lfNum = ins.getLeftNum();
		ins.setLeftNum(lfNum - 1);// 计算器减1

		// 序列号的获取方式：(当前线程第一个序列号值 + 被使用了的序列号数)
		return ins.getNextId() + (bufferSize - lfNum);
	}

	/**
	 * 判断是否需要复位
	 * 
	 * @param no
	 * @param ins
	 * @param cale
	 * @return
	 */
	private boolean needReset(No no, Instance ins, Calendar cale) {
		int year = cale.get(Calendar.YEAR);
		int month = cale.get(Calendar.MONTH) + 1;
		int day = cale.get(Calendar.DAY_OF_MONTH);

		switch (no.getNotype()) {
		case 4: // 按日复位
			if (ins.getYear() != year || ins.getMonth() != month
					|| ins.getDay() != day) {
				return true;
			}
			break;
		case 3: // 按月复位
			if (ins.getYear() != year || ins.getMonth() != month
					|| ins.getDay() != 0) {
				return true;
			}
			break;
		case 2: // 按年复位
			if (ins.getYear() != year || ins.getMonth() != 0
					|| ins.getDay() != 0) {
				return true;
			}
			break;
		case 1: // 不复位
			if (ins.getYear() != 0 || ins.getMonth() != 0 || ins.getDay() != 0) {
				return true;
			}
			break;
		default:
			throw new RuntimeException("不支持的序列定义类型！");
		}
		return false;

	}

	/**
	 * 获得一个指定的序列号定义对象
	 * 
	 * @param seqDefid
	 *            序列号名称
	 * @return
	 */
	private No loadNO(String seqDefid) {
		/* 在cache中取 序列号定义 */

		No no = (No) noMap.get(seqDefid);

		if (no == null && autoCreate) {// 没有在cache中找到序列号的定义。在cache中新增，同时持久化到数据库
			// synchronized (seqDefid.intern()) {
			no = (No) noMap.get(seqDefid);
			// if (no == null) {
			no = buildDefaultNo(seqDefid);// 创建一个默认的序列号
			try {
				sequenceDao.insertNo(no);
			} catch (DataAccessException e) {
				// 主键冲突，有可能是其他应用服务器已经生成了相同的序列号
				log.info("主键冲突，有可能是其他应用服务器已经生成了相同的序列号.所以直接获取该序列号用户生成序列!--"
						+ seqDefid);

				no = sequenceDao.getNo(seqDefid);
			}
			noMap.put(seqDefid, no);
			// }
		}
		if (no == null) {
			throw new RuntimeException("sequence " + seqDefid + " not found");
		}
		// }

		return no;
	}

	public List batchGenerate(String seqDefid, int count) {
		List list = new ArrayList();

		for (int i = 0; i < count; i++) {
			list.add(generate(seqDefid));
		}
		return list;
	}

	/**
	 * 创建一个默认的序列号实例
	 * 
	 * @param seqDefid
	 * @return
	 */
	private Instance buildDefaultInstance(No no, Calendar cale) {

		int year = cale.get(Calendar.YEAR);
		int month = cale.get(Calendar.MONTH) + 1;
		int day = cale.get(Calendar.DAY_OF_MONTH);

		Instance ins = new Instance();
		ins.setNoid(no.getNoid());

		int bufferSize = getBufferSize(no);
		ins.setLeftNum(bufferSize);
		ins.setNextId(1 + bufferSize);
		switch (no.getNotype()) {
		case 4: // 按日复位
			ins.setYear(year);
			ins.setMonth(month);
			ins.setDay(day);
			break;
		case 3: // 按月复位
			ins.setYear(year);
			ins.setMonth(month);
			ins.setDay(0);
			break;
		case 2: // 按年复位
			ins.setYear(year);
			ins.setMonth(0);
			ins.setDay(0);
			break;
		case 1: // 不复位
			ins.setYear(0);
			ins.setMonth(0);
			ins.setDay(0);
			break;
		default:
			throw new RuntimeException("不支持的序列定义类型！");
		}
		return ins;

	}

	private void reset(int type, int year, int month, int day, Instance ins) {
		switch (type) {
		case 4: // 按日复位
			ins.setYear(year);
			ins.setMonth(month);
			ins.setDay(day);
			break;
		case 3: // 按月复位
			ins.setYear(year);
			ins.setMonth(month);
			ins.setDay(0);
			break;
		case 2: // 按年复位
			ins.setYear(year);
			ins.setMonth(0);
			ins.setDay(0);
			break;
		case 1: // 不复位
			ins.setYear(0);
			ins.setMonth(0);
			ins.setDay(0);
			break;
		default:
			throw new RuntimeException("不支持的序列定义类型！");
		}
	}

	/**
	 * 获得buffersize
	 * 
	 * @param no
	 * @return
	 */
	private int getBufferSize(No no) {
		if (no.getBufferSize() > 0) {
			return no.getBufferSize();
		} else {
			return defaultBufferSize;
		}

	}

	/**
	 * 创建一个默认的序列号
	 * 
	 * @param seqDefid
	 * @return
	 */
	private No buildDefaultNo(String seqDefid) {
		No no = new No();
		no.setNoid(seqDefid);
		no.setNoname("auto create");
		no.setNolength(DefaultSequence.DEFAULT_LENGTH);
		no.setNotype(1);
		// 测试
		no.setBufferSize(0);
		return no;
	}

	/**
	 * 根据定义生成序列号字符串
	 * 
	 * @param id
	 * @param nolength
	 * @param prefix
	 * @param postfix
	 * @param cale
	 * @return
	 */
	private String buildSequenceStr(long id, int nolength, String prefix,
			String postfix, Calendar cale) {
		String seq = integerToString(id, nolength);
		if (seq.length() > nolength) {
			throw new IllegalArgumentException("序列号位数溢出！");
		}

		if ((prefix == null) && (postfix == null)) {
			return seq;
		}

		int year = cale.get(Calendar.YEAR);
		int month = cale.get(Calendar.MONTH) + 1;
		int day = cale.get(Calendar.DAY_OF_MONTH);

		String ret = (prefix == null ? "" : prefix) + seq
				+ (postfix == null ? "" : postfix);
		ret = ret.replaceAll("\\{yyyy\\}", integerToString(year, 4));
		ret = ret.replaceAll("\\{yy\\}", integerToString(year, 4).substring(2,
				4));
		ret = ret.replaceAll("\\{mm\\}", integerToString(month, 2));
		ret = ret.replaceAll("\\{dd\\}", integerToString(day, 2));

		return ret;
	}

	private String integerToString(long value, int num) {
		StringBuffer ret = new StringBuffer(Long.toString(value));
		if (ret.length() < num) {
			for (int i = ret.length(); i < num; i++) {
				ret.insert(0, "0");
			}
		}
		return ret.toString();
	}

	public boolean isAutoCreate() {
		return autoCreate;
	}

	public void setAutoCreate(boolean autoCreate) {
		this.autoCreate = autoCreate;
	}

	public void onApplicationEvent(ApplicationEvent event) {

		if (event instanceof SequenceEvent) {
			SequenceEvent sevent = (SequenceEvent) event;

			if (sevent.getEventType() == SequenceEvent.DELETE_NO) {
				synchronized (noMap) {
					noMap.remove(sevent.getNoid());
					instanceMap.remove(sevent.getNoid());
				}
			} else if (sevent.getEventType() == SequenceEvent.UPDATE_NO) {// 更新序列号产生的实例
				synchronized (sevent.getNoid().intern()) {
					noMap.remove(sevent.getNoid());
					instanceMap.remove(sevent.getNoid());
				}

			} else if (sevent.getEventType() == SequenceEvent.UPDATE_NEXTID) {
				synchronized (sevent.getNoid().intern()) {
					instanceMap.remove(sevent.getNoid());
				}
			}
		}
	}

	public SequenceDao getSequenceDao() {
		return sequenceDao;
	}

	public void setSequenceDao(SequenceDao sequenceDao) {
		this.sequenceDao = sequenceDao;
	}

	public int getDefaultBufferSize() {
		return defaultBufferSize;
	}

	public void setDefaultBufferSize(int defaultBufferSize) {
		this.defaultBufferSize = defaultBufferSize;
	}
}
